#include <iostream>
#include <sstream>
#include <fstream>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <float.h>
#include <time.h>
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "tokens.h"
#include "LinkedList.h"
#include "scene.h"
#include "Image.h"
#include "types.h"

#define NUMARGS 5

using namespace std;
using namespace glm;

int SHADING = 0;
int REFLECT = 6;
int ALIAS = 3;
int gcount = 0;

Symbol nextToken(ifstream &file) {
    char c;
    string build = "";
    Symbol ret;

    file >> c;
    while (isspace(c) && file.good()) {
        file >> c;
    }

    build.push_back(c);
    if (isalpha(c)) {
        c = file.peek();
        while(isalpha(c) || c == '_') {
            file >> c;
            build.push_back(c);
            c = file.peek();
        }

        ret.isToken = true;
        ret.t = lookUpToken(build);

        return ret;
    }
    else if (c == '/') {
        file >> c;
        if (c == '/') {
            ret.isToken = true;
            string line;
            getline(file, line);

            return nextToken(file);
        }
        else if (c == '*') {
            file >> c;
            while (file.good()) {
                if (c == '*') {
                    file >> c;
                    if (c == '/') {
                        return nextToken(file);
                    }
                }

                file >> c;
            }
            ret.t = Eof;
            ret.isToken = true;
            return ret;
        }
        
        cout << "incorrect format: found /" << c << " expected //\n";
        exit(1);
    }
    else if (isdigit(c) || c == '.' || c == '-') {
        bool foundDot = (c == '.');
        bool isNeg = (c == '-');

        c = file.peek();
        while (isdigit(c) || c == '.') {
            file >> c;
            build.push_back(c);
            if (c == '.') {
                if (foundDot) {
                    cout << "Incorrect number format\n";
                    exit(1);
                }

                foundDot = true;
            }

            c = file.peek();
        }

        ret.isToken = false;
        istringstream(build) >> ret.num;

        return ret;
    }
    else {
        ret.isToken = true;
        ret.t = lookUpToken(build);

        return ret;
    }
}

Symbol matchToken(ifstream &file, Symbol cur, Token match) {
    if (cur.isToken && cur.t == match) {
        return nextToken(file);
    }
    
    cout << "expected '" << lookUpName(match) << "' found '" <<
        lookUpName(cur.t) << "'" << endl;
    exit(1);
}

float *parseVector(ifstream &file, Symbol curToken, Symbol *nToken,
        int num) {
    float *numFill = new float[num];
    curToken = matchToken(file, curToken, LAngle);

    for (int i = 0; i < num; i++) {
        if (curToken.isToken) {
            cout << "expected number, found " << 
            tokenList[curToken.t] << endl;
            exit(1);
        }
        numFill[i] = curToken.num;
        curToken = nextToken(file);
        if (i < num - 1) 
            curToken = matchToken(file, curToken, Comma); 
    }

    curToken = matchToken(file, curToken, RAngle);

    *(nToken) = curToken;
    return numFill;
}

Symbol WCamera::parse(ifstream &file, Symbol curToken) {
    Symbol nToken = matchToken(file, curToken, LBrace);
    int i;
    float *n;

    while (nToken.t != RBrace) {
        if (nToken.t == Location) {
            nToken = matchToken(file, nToken, Location);
            n = parseVector(file, nToken, &nToken, 3);

            location = vec3(n[0], n[1], n[2]);
            delete n;
        }
        else if (nToken.t == Up) {
            nToken = matchToken(file, nToken, Up);
            n = parseVector(file, nToken, &nToken, 3);
            up = vec3(n[0], n[1], n[2]);
            delete n;
        }
        else if (nToken.t == Right) {
            nToken = matchToken(file, nToken, Right);

            n = parseVector(file, nToken, &nToken, 3);

            right = vec3(n[0], n[1], n[2]);
            delete n;
        }
        else if (nToken.t == LookAt) {
            nToken = matchToken(file, nToken, LookAt);

            n = parseVector(file, nToken, &nToken, 3);

            lookAt = vec3(n[0], n[1], n[2]);
        }
    }

    
    return matchToken(file, nToken, RBrace);
}

void WCamera::print() {
    printf("Camera vectors:\n\tlocation: %f, %f, %f\n\tup"
        ": %f, %f, %f\n\tright: %f, %f, %f\n\tlookAt: %f, %f, %f\n",
        location.x, location.y, location.z, 
        up.x, up.y, up.z,
        right.x, right.y, right.z, 
        lookAt.x, lookAt.y, lookAt.z);
}


Symbol WLight::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);
    
    numFill = parseVector(file, curToken, &curToken, 3);
    location = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Color);
    curToken = matchToken(file, curToken, RGB);


    numFill = parseVector(file, curToken, &curToken, 3);
    curToken = matchToken(file, curToken, RBrace);
    color = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

   
    return curToken;
}

void WLight::print() {
    printf("Light vectors:\n\tlocation: %f, %f, %f\n\tcolor"
        ": %f, %f, %f\n",
        location.x, location.y, location.z, 
        color.x, color.y, color.z);
}



/* global because we're cheating */
Scene globalScene;

Symbol parsePigment(ifstream &file, Symbol curToken, MPigment *p) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);
    curToken = matchToken(file, curToken, Color);

    if (curToken.t == RGB) {
        curToken = matchToken(file, curToken, RGB);

        numFill = parseVector(file, curToken, &curToken, 3);
        p->r = numFill[0];
        p->g = numFill[1];
        p->b = numFill[2];
        p->f = 0.0;
        delete numFill;
    }
    else {
        curToken = matchToken(file, curToken, RGBF);
        numFill = parseVector(file, curToken, &curToken, 4);
        p->r = numFill[0];
        p->g = numFill[1];
        p->b = numFill[2];
        p->f = numFill[3];
        delete numFill;
    }

    

    return matchToken(file, curToken, RBrace);
}

Symbol parseFinish(ifstream &file, Symbol curToken, MFinish *f) {
    curToken = matchToken(file, curToken, LBrace);

    while (curToken.t != RBrace) {
        if (curToken.t == Ambient) {
            curToken = matchToken(file, curToken, Ambient);
            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->ambient = curToken.num;
            curToken = nextToken(file);
        }

        if (curToken.t == Diffuse) {
            curToken = matchToken(file, curToken, Diffuse);
            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->diffuse = curToken.num;
            curToken = nextToken(file);
        }

        if (curToken.t == Specular) {
            curToken = matchToken(file, curToken, Specular);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->specular = curToken.num;
            curToken = nextToken(file);
        }
        
        if (curToken.t == Roughness) {
            curToken = matchToken(file, curToken, Roughness);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->roughness = curToken.num;
            curToken = nextToken(file);
        }

        if (curToken.t == Reflection) {
            curToken = matchToken(file, curToken, Reflection);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->reflection = curToken.num;
            curToken = nextToken(file);
        }

        if (curToken.t == Refraction) {
            curToken = matchToken(file, curToken, Refraction);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->refraction = curToken.num;
            curToken = nextToken(file);
        }


        if (curToken.t == IOR) {
            curToken = matchToken(file, curToken, IOR);

            if (curToken.isToken) {
                cout << "expected number, found " << tokenList[curToken.t]
                    << endl;
                exit(1);
            }

            f->ior = curToken.num;
            curToken = nextToken(file);
        }
    }

    return matchToken(file, curToken, RBrace);
}

Symbol parseTranslate(ifstream &file, Symbol curToken, mat4 *trans) {
    float *fillNum;

    fillNum = parseVector(file, curToken, &curToken, 3);

    *(trans) = translate(mat4(1.0f), 
            vec3(fillNum[0], fillNum[1], fillNum[2])) * (*trans);

    delete fillNum;
    return curToken;
}

Symbol parseRotate(ifstream &file, Symbol curToken, mat4 *trans) {
    float *fillNum;

    fillNum = parseVector(file, curToken, &curToken, 3);

    *(trans) = 
        rotate(mat4(1.0), fillNum[2], vec3(0.0f, 0.0f, 1.0f)) *
        rotate(mat4(1.0), fillNum[1], vec3(0.0f, 1.0f, 0.0f)) *
        rotate(mat4(1.0), fillNum[0], vec3(1.0f, 0.0f, 0.0f)) * (*trans);

    delete fillNum;
    return curToken;
}

Symbol parseScale(ifstream &file, Symbol curToken, mat4 *trans) {
    float *fillNum;
    
    fillNum = parseVector(file, curToken, &curToken, 3);

    *(trans) = scale(mat4(1.0), vec3(fillNum[0], fillNum[1], fillNum[2])) *
        (*trans);

    delete fillNum;
    return curToken;
}

Symbol parseMaterials(ifstream &file, Symbol curToken, Geometry *g) {
    while (curToken.t != RBrace) {
        if (curToken.t == Pigment) {
            curToken = parsePigment(file, 
                matchToken(file, curToken, Pigment), &(g->pigment));
        }
        else if (curToken.t == Finish) {
            curToken = parseFinish(file,
                matchToken(file, curToken, Finish), &(g->finish));
        }
        else if (curToken.t == Translate) {
            curToken = parseTranslate(file,
                matchToken(file, curToken, Translate), &(g->transform));
        }
        else if (curToken.t == Rotate) {
            curToken = parseRotate(file,
                matchToken(file, curToken, Rotate), &(g->transform));
        }
        else if (curToken.t == Scale) {
            curToken = parseScale(file,
                matchToken(file, curToken, Scale), &(g->transform));
        }
        else {
            /*if (curToken.isToken)
                cout << tokenList[curToken.t] << endl;
            else cout << curToken.num << endl;
            curToken = nextToken(file);*/
            cout << "Unexpected token: " << tokenList[curToken.t] << endl;
            exit(1);
        }
    }

    g->transform = inverse(g->transform);

    return matchToken(file, curToken, RBrace);
}

float smallest_positive_valid(int num, float *arr, vec3 p, vec3 d, vec3 min,
        vec3 max) {
    int i;
    float m = -1;
    vec3 val;

    for (i = 0; i < num; i++) {
        val = p + arr[i] * d;
        if (val.x < min.x - 0.001 || val.x > max.x + 0.001 || 
            val.y < min.y - 0.001 || val.y > max.y + 0.001 ||
            val.z < min.z - 0.001 || val.z > max.z + 0.001) {
            continue;
        }
        if (m == -1 && arr[i] > 0.f) {
            m = arr[i];
        }
        else if (arr[i] > 0.f && arr[i] < m) {
            m = arr[i];
        }
    }

    return m;
}

int smallest_index(int num, float *arr) {
    int i;
    float min = arr[0];
    int minRet = 0;

    for (i = 1; i < num; i++) {
        if (arr[i] < min) {
            min = arr[i];
            minRet = i;
        }
    }

    return minRet;
}

Symbol WSphere::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);

    numFill = parseVector(file, curToken, &curToken, 3);
    center = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    if (curToken.isToken) {
        cout << "expected number, found " 
            << tokenList[curToken.t] << endl;
        exit(1);
    }

    radius = curToken.num;
    curToken = nextToken(file);

    curToken = parseMaterials(file, curToken, this);
    return curToken;
}

bool WSphere::intersect(Ray *r) {
    vec3 PtoC, point, d;
    vec4 pprime, dprime;
    float A;
    float B;
    float C;
    float t, t1, t2;
    float deter;

    pprime = transform * vec4(r->point.x, r->point.y, r->point.z, 1.0);
    dprime = transform * vec4(r->d.x, r->d.y, r->d.z, 0.0);
    point = vec3(pprime.x, pprime.y, pprime.z);
    d = vec3(dprime.x, dprime.y, dprime.z);

    PtoC = point - center;
    A = dot(d, d);
    B = 2 * dot(d, PtoC);
    C = dot(PtoC, PtoC) - radius * radius;

    deter = B * B - 4 * A * C;

    if (deter < 0) {
        return false;
    }

    t1 = (-B + sqrt(deter))/(2 * A);
    t2 = (-B - sqrt(deter))/(2 * A);
    if (t2 > 0.001 && t2 < t1) {
        t = t2;
    }
    else {
        t = t1;
    }

    if (t > 0 && t < r->t) {
        r->t = t;
        return true;
    }

    return false;
}

vec3 WSphere::getNormal(Ray *r) {
    vec3 point, d, n;
    vec4 pprime, dprime, normal;

    pprime = transform * vec4(r->point.x, r->point.y, r->point.z, 1.0);
    dprime = transform * vec4(r->d.x, r->d.y, r->d.z, 0.0);
    point = vec3(pprime.x, pprime.y, pprime.z);
    d = vec3(dprime.x, dprime.y, dprime.z);

    n = point + r->t * d - center;
    normal = transpose(transform) * vec4(n.x, n.y, n.z, 0.0);

    return normalize(vec3(normal.x, normal.y, normal.z));
}

Symbol WPlane::parse(ifstream &file, Symbol curToken) {
    float *numFill;
    float d;

    curToken = matchToken(file, curToken, LBrace);

    numFill = parseVector(file, curToken, &curToken, 3);
    normal = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    if (curToken.isToken) {
        cout << "Expected number: found " 
            << tokenList[curToken.t] << endl;
        exit(1);
    }

    distance = curToken.num;
    curToken = nextToken(file);
    curToken = parseMaterials(file, curToken, this);

    d = -distance / (dot(normal, normal));
    pointOnPlane = vec3(d * normal.x, d * normal.y, d * normal.z);

    return curToken;
}

bool WPlane::intersect(Ray *r) {
    vec3 d, point;
    vec3 n;
    vec4 pprime, dprime;
    float t;

    pprime = transform * vec4(r->point.x, r->point.y, r->point.z, 1.0);
    dprime = transform * vec4(r->d.x, r->d.y, r->d.z, 0.0);
    point = vec3(pprime.x, pprime.y, pprime.z);
    d = vec3(dprime.x, dprime.y, dprime.z);
    n = normal;

    if (dot(n, d) < 0) {
        n = -n;
    }

    t = -dot((point + pointOnPlane), n)/dot(d, n);

    if (t > 0 && t < r->t) {
        r->t = t;
        return true;
    }

    return false;
}

vec3 WPlane::getNormal(Ray *r) {
    vec4 normalprime;
    vec3 n;

    normalprime = vec4(normal.x, normal.y, normal.z, 0.0);
    normalprime = transform * normalprime;
    n = vec3(normalprime.x, normalprime.y, normalprime.z);
    return normalize(n);
}

Symbol WTriangle::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);

    numFill = parseVector(file, curToken, &curToken, 3);
    p1 = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    numFill = parseVector(file, curToken, &curToken, 3);
    p2 = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    numFill = parseVector(file, curToken, &curToken, 3);
    p3 = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;
    
    curToken = parseMaterials(file, curToken, this);

    return curToken;
}

bool WTriangle::intersect(Ray *r) {
    mat3 A;
    vec3 b;
    vec3 x;
    vec3 d, point;
    vec4 dprime, pprime;
    float alpha, beta, gamma, t;
    float det;
    float m1, m2, m3;
    
    pprime = transform * vec4(r->point.x, r->point.y, r->point.z, 1.0);
    dprime = transform * vec4(r->d.x, r->d.y, r->d.z, 0.0);
    point = vec3(pprime.x, pprime.y, pprime.z);
    d = vec3(dprime.x, dprime.y, dprime.z);


    A[0][0] = p1.x - p2.x;
    A[0][1] = p1.y - p2.y;
    A[0][2] = p1.z - p2.z;
    A[1][0] = p1.x - p3.x;
    A[1][1] = p1.y - p3.y;
    A[1][2] = p1.z - p3.z;
    A[2][0] = d.x;
    A[2][1] = d.y;
    A[2][2] = d.z;

    b = p1 - point;

    x = inverse(A) * b;

/*    det = (p1.x - p2.x) * ((p1.y - p3.y) * d.z + d.y * (p3.z - p1.z)) +
          (p3.x - p1.x) * (d.z * (p1.y - p2.y) + d.y * (p2.z - p1.z)) +
          d.x * ((p1.y - p2.y) * (p1.z - p3.z) + (p3.y - p1.y) * (p1.z- p2.z));

    m1 = (p1.x - p3.x) * d.y - d.x * (p1.y - p3.y);
    m2 = -((p1.x - p2.x) * d.y - d.x * (p1.y - p2.y));
    m3 = ((p1.x - p2.x) * (p1.y - p3.y) - (p1.x - p3.x) * (p1.y - p2.y));*/
    /*t = (((p1.x - p3.x) * d.y + d.x * (p3.y - p1.y)) * (p1.x - point.x) +
        (d.x * (p1.y - p2.y) + (p2.x - p1.x) * d.y) * (p1.y - point.y) +
        ((p1.x - p2.x) * (p1.y - p3.y) + (p3.x - p1.x) * (p1.y - p2.y)) *
         (p1.z - point.z)) / det;*/
/*    t = m1 * (p1.x - point.x) + m2 * (p1.y - point.y) + m3 * (p1.z - point.z);
    t = t / det;*/


    t = x[2];
    if (t < 0.0 || t > r->t)
        return false;

    beta = x[0];
    /*beta = 
     (((p1.y - p3.y) * d.z - d.y * (p3.z - p1.z)) * (p1.x - point.x) +
     (d.y * (p2.z - p1.z) + (p2.y - p1.y) * d.z) * (p1.y - point.y) +
     ((p1.y - p2.y) * (p1.z - p3.z) + (p3.y - p1.y) * (p1.z - p2.z)) *
      (p1.z - point.z)) / det;*/

    if (beta < 0.0 || beta > 1.0) {
        return false;
    }

    gamma = x[1];
    /*gamma =
     (((p3.x - p1.x) * d.z + d.x * (p1.z - p3.z)) * (p1.x - point.x) +
     ((p1.x - p2.x) * d.z - d.x * (p1.z - p2.z)) * (p1.y - point.y) +
     ((p1.x - p2.x) * (p1.z - p3.z) + (p3.x - p1.x) * (p1.z - p2.z)) *
      (point.z - p1.z)) / det;*/

    if (gamma < 0.0 || gamma > 1.0) {
        return false;
    }

    alpha = 1 - beta - gamma;

    if (alpha < 0.0 || alpha > 1.0) {
        return false;
    }

    r->t = t;
    return true;
}

vec3 WTriangle::getNormal(Ray *r) {
    vec3 n;
    float test;
    vec4 normal;

    n = cross(p2 - p1, p3 - p1);
    normal = vec4(n.x, n.y, n.z, 0.0);
    normal = transpose(transform) * normal;
    n = vec3(normal.x, normal.y, normal.z);

    return normalize(n);
}

Symbol WCone::parse(ifstream &file, Symbol curToken) {
    float *numFill;
    
    curToken = matchToken(file, curToken, LBrace);
    numFill = parseVector(file, curToken, &curToken, 3);
    base = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    if (curToken.isToken) {
        cout << "Expected number, found " 
            << tokenList[curToken.t] << endl;
        exit(1);
    }
    baseR = curToken.num;

    curToken = nextToken(file);
    curToken = matchToken(file, curToken, Comma);
    numFill = parseVector(file, curToken, &curToken, 3);
    top = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    if (curToken.isToken) {
        cout << "Expected number, found " 
            << tokenList[curToken.t] << endl;
        exit(1);
    }
    topR = curToken.num;
    
    curToken = nextToken(file);
    curToken = parseMaterials(file, curToken, this);

    return curToken;
}

bool WCone::intersect(Ray *r) {
    return false;
}

vec3 WCone::getNormal(Ray *r) {
    return vec3(0.0);
}

Symbol WBox::parse(ifstream &file, Symbol curToken) {
    float *numFill;

    curToken = matchToken(file, curToken, LBrace);
    numFill = parseVector(file, curToken, &curToken, 3);
    min = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = matchToken(file, curToken, Comma);
    numFill = parseVector(file, curToken, &curToken, 3);
    max = vec3(numFill[0], numFill[1], numFill[2]);
    delete numFill;

    curToken = parseMaterials(file, curToken, this);

    if (min.x > max.x) {
        max.x += min.x;
        min.x = max.x - min.x;
        max.x = max.x - min.x;
    }

    if (min.y > max.y) {
        max.y += min.y;
        min.y = max.y - min.y;
        max.y = max.y - min.y;
    }

    if (min.z > max.z) {
        max.z += min.z;
        min.z = max.z - min.z;
        max.z = max.z - min.z;
    }

    return curToken;
}

bool WBox::intersect(Ray *r) {
    vec4 pPrime, dPrime;
    vec3 p, d;
    float gmin, gmax;
    float xmin = FLT_MIN, xmax = FLT_MAX, 
          ymin = FLT_MIN, ymax = FLT_MAX, 
          zmin = FLT_MIN, zmax = FLT_MAX;

    float arr[6];
    float t;


    pPrime = transform * vec4(r->point.x, r->point.y, r->point.z, 1.0);
    dPrime = transform * vec4(r->d.x, r->d.y, r->d.z, 0.0);
    p = vec3(pPrime.x, pPrime.y, pPrime.z);
    d = vec3(dPrime.x, dPrime.y, dPrime.z);

  /*  if (d.x == 0.0f || d.y == 0.0f || d.z == 0.0f) {
        cout << "THIS HAPPENED\n";
        if (d.x > 0.0f && p.x < max.x) {
            xmax = (max.x - p.x) / d.x;
        }
        else if (d.x < 0.0f && p.x > min.x) {
            xmin = (min.x - p.x) / d.x;
        }
        
        if (d.y > 0.0f && p.y < max.y) {
            ymax = (max.y - p.y) / d.y;
        }
        else if (d.y < 0.0f && p.y > min.y) {
            ymin = (min.y - p.y) / d.y;
        }

        if (d.z > 0.0f && p.z < max.z) {
            zmax = (max.z - p.z) / d.z;
        }
        else if (d.z < 0.0f && p.z > min.z) {
            zmin = (min.z - p.z) / d.z;
        }
    }
    else {*/
        xmax = (max.x - p.x) / d.x;
        ymax = (max.y - p.y) / d.y;
        zmax = (max.z - p.z) / d.z;
        xmin = (min.x - p.x) / d.x;
        ymin = (min.y - p.y) / d.y;
        zmin = (min.z - p.z) / d.z;
//    }

    if (xmax < xmin) {
        /* two variable swap. keeps us in 2 registers
         * this should be a safe operation (no overflow)
         * because FLT_MAX is absolutely ginormous 
         */
        xmax = xmax + xmin;
        xmin = xmax - xmin;
        xmax = xmax - xmin;
    }

    if (ymax < ymin) {
        ymax = ymax + ymin;
        ymin = ymax - ymin;
        ymax = ymax - ymin;
    }

    if (zmax < zmin) {
        zmax = zmax + zmin;
        zmin = zmax - zmin;
        zmax = zmax - zmin;
    }

    gmax = std::min(std::min(xmax, ymax), zmax);
    gmin = std::max(std::max(xmin, ymin), zmin);

    if (gmin < gmax) {
        arr[0] = xmin;
        arr[1] = xmax;
        arr[2] = ymin;
        arr[3] = ymax;
        arr[4] = zmin;
        arr[5] = zmax;


        t = smallest_positive_valid(6, arr, p, d, min, max); 
        if (t > 0.0 && t < r->t) {
            r->t = t;
            return true;
        }
    }

    return false;
}

vec3 WBox::getNormal(Ray *r) {
    vec3 p = r->point + r->t * r->d;
    vec3 d;
    vec4 dPrime, pPrime;
    vec4 n;
    float arr[6];
    int i;

    pPrime = transform * vec4(r->point.x, r->point.y, r->point.z, 1.0);
    dPrime = transform * vec4(r->d.x, r->d.y, r->d.z, 0.0);
    p = vec3(pPrime.x, pPrime.y, pPrime.z);
    d = vec3(dPrime.x, dPrime.y, dPrime.z);
    p = p + r->t * d;

    /* dxmin */
    arr[0] = abs(p.x - min.x);
    /* dxmax */
    arr[1] = abs(p.x - max.x);
    /* dymin */
    arr[2] = abs(p.y - min.y);
    /* dymax */
    arr[3] = abs(p.y - max.y);
    /* dzmin */
    arr[4] = abs(p.z - min.z);
    /* dzmax */
    arr[5] = abs(p.z - max.z);

    i = smallest_index(6, arr);

    switch(i) {
        case 0:
            n = vec4(-1.0f, 0.f, 0.f, 0.f);
            break;
        case 1:
            n = vec4(1.f, 0.f, 0.f, 0.f);
            break;
        case 2:
            n = vec4(0.f, -1.f, 0.f, 0.f);
            break;
        case 3:
            n = vec4(0.f, 1.f, 0.f, 0.f);
            break;
        case 4:
            n = vec4(0.f, 0.f, -1.f, 0.f);
            break;
        case 5:
            n = vec4(0.f, 0.f, 1.f, 0.f);
            break;
        default:
            n = vec4(0.0);
            break;
    }

    n = transpose(transform) * n;
    return normalize(vec3(n.x, n.y, n.z));
}

Symbol parseGeometry(ifstream &file, Symbol curToken) {
    Geometry *g;
    if (curToken.t == Sphere) {
        g = new WSphere();
        curToken = g->parse(file, matchToken(file, curToken, Sphere));
    }
    else if (curToken.t == Plane) {
        g = new WPlane();
        curToken = g->parse(file, matchToken(file, curToken, Plane));
    }
    else if (curToken.t == Triangle) {
        g = new WTriangle();
        curToken = g->parse(file, matchToken(file, curToken, Triangle));
    }
    else if (curToken.t == Box) {
        g = new WBox();
        curToken = g->parse(file, matchToken(file, curToken, Box));
    }
    else if (curToken.t == Cone) {
        g = new WCone();
        curToken = g->parse(file, matchToken(file, curToken, Cone));
    }
    else {
        if (curToken.isToken) {
            cout << "Expected Object, found " << 
            tokenList[curToken.t] << endl;
        }
        else {
            cout << "Expected Object, found " << curToken.num << endl;
        }
        exit(1);
    }

    globalScene.geometry.add(g);
    return curToken;
}

Symbol parseObject(ifstream& file, Symbol curToken) {
    if (curToken.t == Camera) {
        WCamera *c = new WCamera();
        curToken = c->parse(file, matchToken(file, curToken, Camera));
        if (globalScene.camera != NULL) {
            cout << "Cannot define multiple cameras\n";
            exit(1);
        }

        globalScene.camera = c;
        return curToken;
    }
    else if (curToken.t == Light) {
        WLight *l = new WLight();
        curToken = l->parse(file, matchToken(file, curToken, Light));
        globalScene.lights.add(l);

        return curToken;
    }
    else {
        return parseGeometry(file, curToken);
    }
}

Symbol parseStatement(ifstream &file, Symbol curToken) {
    if (curToken.isToken && curToken.t != Eof) {
        return parseObject(file, curToken);
    }
    else if (curToken.t != Eof) {
        cout << "Expected object, found number: " << curToken.num << endl;
        exit(1);
    }
    else {
        return curToken;
    }
}

void parseScene(ifstream &file) {
    Symbol curToken = nextToken(file);
    while (curToken.t != Eof && file.good()) {
        curToken = parseStatement(file, curToken);
    }
}

/* for rotating the camera */
mat4 lookTowards(vec3 up, vec3 lookAt, vec3 location) {
    /* centers our coordinates around location */
    vec3 loc = lookAt - location;
    vec3 axis = cross(up, loc);
    mat4 ret = mat4(1.f);
    /* how much to rotate around the Y-axis */
    /* I set it to 0 here because it turned out to be wrong when it wasn't 0 */
    float rotY = (loc.z == 0.0 && loc.x == 0.0) || 1 ? 0.0 :
    atan2(loc.x, loc.z) * 180.0/3.1415926 -90.0;
    float rotA = 90 - asin(length(axis)/length(loc)) * 180.0 / 3.1415926;

    if (loc.y > 0) {
        rotA = -rotA;
    }

    ret = rotate(ret, rotA, axis);
    ret = rotate(ret, rotY, up);
    return ret;
}

class Raytracer {
public: 
    Scene *scene;
    string output;
    Image *image;
    float width, height;
    Ray **rays;

    Raytracer(int w, int h, string file, Scene *s) {
        scene = s;
        image = new Image(w, h);
        output = file;
        width = w;
        height = h;
        rays = (Ray **)malloc(sizeof(Ray *) * width);

        for (int i = 0; i < width; i++) {
            rays[i] = (Ray *)malloc(sizeof(Ray) * height);
        }
    }

    void generateRays() {
        float left = -length(scene->camera->right)/2.0;
        //-((float) width)/height/2.0;
        float right = -left;
        float bot = -length(scene->camera->up)/2.0;
        float top = -bot;
        mat4 lookat;
        vec3 t;
        vec3 u, v, w;
        vec4 tPrime;

        /*if (((float) width)/height > 1) {
            left = -0.5;
            right = 0.5;
            bot = -((float) height)/width/2.0;
            top = -bot;
        }*/

        lookat = lookTowards(scene->camera->up, scene->camera->lookAt,
                scene->camera->location);
        
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                t = vec3(left + (right - left) * ((float) i + 0.5)/width,
                         bot + (top - bot) * ((float) j + 0.5)/height,
                         -1.0);

                u = normalize(scene->camera->right);
                v = normalize(scene->camera->up);
                w = cross(u, v);

                rays[i][j].point = scene->camera->location;
                t = rays[i][j].point + 
                    (left + (right - left) * 
                    ((float) i + 0.5f)/(float) width) * u +
                    (bot + (top - bot) * 
                    ((float) j + 0.5f)/(float) height) * v +
                    -1.0f * w;


                t = normalize(t - scene->camera->location);
                tPrime = lookat * vec4(t.x, t.y, t.z, 0.0);
                t = vec3(tPrime.x, tPrime.y, tPrime.z);

                rays[i][j].d = t;
                rays[i][j].t = FLT_MAX;
                rays[i][j].color = vec4(0.0, 0.0, 0.0, 0.0);
            }
        }
    }

    Geometry *intersectScene(Ray *r) {
        Geometry *ret = NULL;
        Geometry *temp;
        
        scene->geometry.reset();
        while(scene->geometry.hasNext()) {
            temp = scene->geometry.getNext();
            if (temp->intersect(r)) {
                ret = temp;
            }
        }

        return ret;
    }

    color_t shadePhong(Geometry *g, WLight *l, Ray *r);
    color_t shadeGaussian(Geometry *g, WLight *l, Ray *r);
    color_t shadeBeckmann(Geometry *g, WLight *l, Ray *r);
    color_t shadeCookTorrance(Geometry *g, WLight *l, Ray *r);

    color_t normalShading(Geometry *g, Ray *r, int ref);
    color_t refractiveShading(Geometry *g, Ray *r, int ref);

    color_t lightRay(Geometry *g, Ray *r, int ref) {
        color_t clr;
        /* beer's law attenuation */
        float atten;
        float dDotd;

        clr.r = 0.0;
        clr.g = 0.0;
        clr.b = 0.0;
        clr.f = 0.0;
        if (!g) {
            return clr;
        }
        
        if (g->finish.refraction == 0.0) {
            clr = normalShading(g, r, ref);

            /*clr.r += g->pigment.r * g->finish.ambient;
            clr.g += g->pigment.g * g->finish.ambient;
            clr.b += g->pigment.b * g->finish.ambient;*/
        }
        else {
            clr = refractiveShading(g, r, ref);

            /* this ray is entering the material */
            if (dot(-r->d, g->getNormal(r)) < 0) {
                atten = exp(-r->t * g->finish.ambient * (g->pigment.r));
                clr.r = clr.r * atten /*+ 
                    g->pigment.r * g->finish.ambient * atten*/;

                atten = exp(-r->t * g->finish.ambient * (g->pigment.g));
                clr.g = clr.g * atten /*+ 
                    g->pigment.g * g->finish.ambient * atten*/;

                atten = exp(-r->t * 0.15 * (g->pigment.b));
                clr.b = clr.b * atten /*+ 
                    g->pigment.b * g->finish.ambient * atten*/;
            }
        }

        clr.f = g->pigment.f;
        return clr;
    }

    vec2 jitter(float min, float max) {
        float q;
        vec2 ret;

        q = ((float) rand()) / RAND_MAX;
        ret.x = min - (min - max) * q;
        q = ((float) rand()) / RAND_MAX;
        ret.y = min - (min - max) * q;

        return ret;
    }

    void trace() {
        Ray r;
        Geometry *g;
        WLight *l;
        color_t clr, clr_write;
        int i, j, index;
        float delta, x, y;
        float max;
        float percent = 0.0;
        mat4 lookat;
        vec3 t, u, v, w;
        vec4 tPrime;
        float right = length(scene->camera->right) / 2.0;
        float left = -right;;
        float top = length(scene->camera->up) / 2.0;
        float bot = -top;
        vec2 prev, next;
        vec2 middle;

        lookat = lookTowards(scene->camera->up, scene->camera->lookAt,
                scene->camera->location);
        delta = 1.0 / ALIAS;

        u = normalize(scene->camera->right);
        v = normalize(scene->camera->up);
        w = cross(u, v);

        
        cout << endl;
        for (i = 0; i < width; i++) {
            for (j = 0; j < height; j++) {
                clr_write.r = 0.0;
                clr_write.g = 0.0;
                clr_write.b = 0.0;
                clr_write.f = 0.0;
                
 
                for (index = 0; index < ALIAS * ALIAS; index++) {
                    x = index % ALIAS;
                    y = index / ALIAS;
                    prev = vec2(x * delta, y * delta);
                    next = vec2(x * delta + delta, y * delta + delta);
                    middle = (prev + next) / 2.0f;
                    if (ALIAS != 1)
                        middle += jitter(-delta / 4.0, delta / 4.0);

                    r.point = scene->camera->location;
                    t = r.point + 
                        (left + (right - left) *
                         ((float) i + middle.x) / width) * u +
                        (bot + (top - bot) * 
                         ((float) j + middle.y) / height) * v +
                        -1.0f * w;

                    t = normalize(t - scene->camera->location);
                    tPrime = lookat * vec4(t.x, t.y, t.z, 0.0);
                    t = vec3(tPrime.x, tPrime.y, tPrime.z);

                    r.d = t;
                    r.t = FLT_MAX;

                    g = intersectScene(&r);
                    clr.r = 0.0;
                    clr.g = 0.0;
                    clr.b = 0.0;
                    clr.f = 0.0;
                    
                    if (g) {
                        clr = lightRay(g, &r, REFLECT);
                    }

                    max = std::max(std::max(clr.r, clr.g), clr.b);
                    if (max > 1.0) {
                        clr.r /= max;
                        clr.g /= max;
                        clr.b /= max;
                    }

                    clr_write.r += clr.r;
                    clr_write.g += clr.g;
                    clr_write.b += clr.b;
                }

               clr_write.r /= ALIAS * ALIAS;
                clr_write.g /= ALIAS * ALIAS;
                clr_write.b /= ALIAS * ALIAS;

                image->pixel(i, j, clr_write);
            }
            if (isatty(1)) {
                percent = (i * height + j) / (width * height);
                printf("Complete: %%%f\r", percent * 100.0);
            }
        }

        printf("Complete: %%100                  \n");
        image->WriteTga((char *)output.c_str());
    }
};

color_t Raytracer::normalShading(Geometry *g, Ray *r, int ref) {
    Ray *rShadow, *rBounce;
    Geometry *gShadow, *gBounce;
    WLight *l;
    color_t clr, clr1, clr2;
    vec3 normal;
    float dDotn;
    
    clr.r = 0.0;
    clr.g = 0.0;
    clr.b = 0.0;
    clr.f = 0.0;

    clr1.r = 0.0;
    clr1.g = 0.0;
    clr1.b = 0.0;
    clr1.f = 0.0;

    clr2.r = 0.0;
    clr2.g = 0.0;
    clr2.b = 0.0;
    clr2.f = 0.0;

    rShadow = new Ray();
    scene->lights.reset();
    while (scene->lights.hasNext()) {
        l = scene->lights.getNext();

        rShadow->point = r->point + r->t * r->d;
        rShadow->d = 
            normalize(l->location - rShadow->point);
        /* prevent self shadowing */
        rShadow->point += g->getNormal(r) * 0.01f;
        rShadow->t = FLT_MAX;

        gShadow = intersectScene(rShadow);
        
        if (!gShadow) {
            if (SHADING == 0) {
                clr = shadePhong(g, l, r);
            }
            else if (SHADING == 1) {
                clr = shadeGaussian(g, l, r);
            }
            else if (SHADING == 2) {
                clr = shadeBeckmann(g, l, r);
            }
            else if (SHADING == 3) {
                clr = shadeCookTorrance(g, l, r);
            }

            clr1.r += clr.r;
            clr1.g += clr.g;
            clr1.b += clr.b;

        }
        else if (gShadow->finish.refraction != 0.0) {
            clr1.r += 
                g->pigment.r * g->finish.diffuse * g->finish.ambient * 0.2;
            clr1.g += 
                g->pigment.g * g->finish.diffuse * g->finish.ambient * 0.2;
            clr1.b += 
                g->pigment.b * g->finish.diffuse * g->finish.ambient * 0.2;
        }
        else {
            /*clr1.r = -g->pigment.r * g->finish.ambient;
            clr1.g = -g->pigment.g * g->finish.ambient;
            clr1.b = -g->pigment.b * g->finish.ambient;

            /*printf("this\n");
            g->print();
            printf("BLOCKED BY\n");
            gShadow->print();*/
        }
    }
    delete rShadow;

    normal = g->getNormal(r);
    dDotn = dot(-r->d, normal);
    if (g->finish.reflection && ref) {
        if (dDotn < 0) {
            normal = -normal;
            dDotn = dot(-r->d, normal);
        }

        rBounce = new Ray();
        rBounce->d = normalize(r->d +
                2.0f * dDotn * normal);
        rBounce->t = FLT_MAX;
        /* move bounce off of the geometry a little */
        rBounce->point = r->point + r->t * r->d +
            normal * 0.01f;

        gBounce = intersectScene(rBounce);
        if (gBounce) {
            clr2 = lightRay(gBounce, rBounce, ref - 1);
        }
        delete rBounce;
    }

    clr.r = clr1.r + clr2.r * g->finish.reflection;
    clr.g = clr1.g + clr2.g * g->finish.reflection;
    clr.b = clr1.b + clr2.b * g->finish.reflection;
    clr.f = 0.0;

    clr.r += g->pigment.r * g->finish.ambient;
    clr.g += g->pigment.g * g->finish.ambient;
    clr.b += g->pigment.b * g->finish.ambient;


    return clr;
}

color_t Raytracer::refractiveShading(Geometry *g, Ray *r, int ref) {
    Ray *rShadow, *rBounce, *rRefract;
    Geometry *gShadow, *gBounce, *gRefract;
    WLight *l;
    color_t clr, clr1, clr2, clr3;
    vec3 normal;
    float deter, fresnel = 1.0;
    float n1, n2;
    float dDotn, R0;


    clr.r = 0.0;
    clr.g = 0.0;
    clr.b = 0.0;
    clr.f = 0.0;

    clr1.r = 0.0;
    clr1.g = 0.0;
    clr1.b = 0.0;
    clr1.f = 0.0;

    clr2.r = 0.0;
    clr2.g = 0.0;
    clr2.b = 0.0;
    clr2.f = 0.0;

    clr3.r = 0.0;
    clr3.g = 0.0;
    clr3.b = 0.0;
    clr3.f = 0.0;

    normal = g->getNormal(r);
    dDotn = dot(-r->d, normal);
    /* we're exiting the object */
    if (dDotn < 0) {
        n1 = g->finish.ior;
        n2 = 1.0;
        normal = -normal;
        dDotn = dot(-r->d, normal);
    }
    else {
        n1 = 1.0;
        n2 = g->finish.ior;
    }

    if (ref) {
        rBounce = new Ray();
        rBounce->d = normalize(r->d +
                2.0f * dDotn * normal);
        rBounce->t = FLT_MAX;
        rBounce->point = r->point + r->t * r->d +
                normal * 0.01f;

        gBounce = intersectScene(rBounce);
        if (gBounce) {
            clr1 = lightRay(gBounce, rBounce, ref - 1);
        }

        delete rBounce;

        R0 = (n1 - n2) * (n1 - n2) / ((n1 + n2) * (n1 + n2));
        fresnel = R0 + (1.0f - R0) * (1.0f - 
                dDotn * dDotn);
        deter = 1.0f - (n1 * n1)/(n2 * n2) * (1.0f - 
                dDotn * dDotn * dDotn * dDotn * dDotn);
        /* total internal reflection */
        if (deter < 0.f) {
            fresnel = 1.0;
        }
        else {
            rRefract = new Ray();
            rRefract->d = normalize(n1/n2 * 
                (r->d + normal * dDotn) * sqrt(1.f + dDotn * dDotn)
                - normal * sqrt(deter));
            gcount++;

            if (rRefract->d.x < 0.001 && rRefract->d.x > -0.001 &&
                rRefract->d.y < 0.001 && rRefract->d.y > -0.001 &&
                rRefract->d.z < 0.001 && rRefract->d.z > -0.001) {
                rRefract->d = r->d;
            }
            rRefract->t = FLT_MAX;
            rRefract->point = r->point + (r->t + 0.01f) * r->d;
        
            gRefract = intersectScene(rRefract);
            if (gRefract) {
                clr2 = lightRay(gRefract, rRefract, ref - 1);
            }

            delete rRefract;
        }
    }

    rShadow = new Ray();
    scene->lights.reset();
    while (scene->lights.hasNext()) {
        l = scene->lights.getNext();

        rShadow->point = r->point + r->t * r->d;
        rShadow->d = 
            normalize(l->location - rShadow->point);
        /* prevent self shadowing */
        rShadow->point += rShadow->d * 0.01f;
        rShadow->t = FLT_MAX;

        gShadow = intersectScene(rShadow);
        
        if (1 || !gShadow) {
            if (SHADING == 0) {
                clr = shadePhong(g, l, r);
            }
            else if (SHADING == 1) {
                clr = shadeGaussian(g, l, r);
            }
            else if (SHADING == 2) {
                clr = shadeBeckmann(g, l, r);
            }
            else if (SHADING == 3) {
                clr = shadeCookTorrance(g, l, r);
            }

            clr3.r += clr.r;
            clr3.g += clr.g;
            clr3.b += clr.b;
        }
        else if (gShadow->finish.refraction != 0.0) {
            clr3.r += 
                g->pigment.r * g->finish.diffuse * g->finish.ambient * 0.2;
            clr3.g += 
                g->pigment.g * g->finish.diffuse * g->finish.ambient * 0.2;
            clr3.b += 
                g->pigment.b * g->finish.diffuse * g->finish.ambient * 0.2;
        }
    }
    delete rShadow;

    clr.r = clr1.r * fresnel + clr2.r * (1.f - fresnel) + 
        clr3.r * g->pigment.f;
    clr.g = clr1.g * fresnel + clr2.g * (1.f - fresnel) + 
        clr3.g * g->pigment.f;
    clr.b = clr1.b * fresnel + clr2.b * (1.f - fresnel) +
        clr3.b * g->pigment.f;


    clr.r += g->pigment.r * g->finish.ambient;
    clr.g += g->pigment.g * g->finish.ambient;
    clr.b += g->pigment.b * g->finish.ambient;

    return clr;
}

color_t Raytracer::shadePhong(Geometry *g, WLight *l, Ray *r) {
    vec3 normal = normalize(g->getNormal(r));
    vec3 lightDir = normalize(l->location - (r->point + r->t * r->d));
    float NDotL = std::max(0.f, dot(normal, lightDir));
    vec3 R = -lightDir + 2.f * normal * NDotL;
    float VDotR = std::max(0.f, dot(-r->d, R));
    float kspec = 0.0;
    color_t ret;

    if (g->finish.specular && g->finish.roughness) {
        kspec = pow(VDotR, 1.0f/ g->finish.roughness);
    }

    ret.r = 
        (g->pigment.r * g->finish.diffuse) * l->color.x * NDotL +
        (g->pigment.r * g->finish.specular) * l->color.x * kspec;
    ret.g = 
        (g->pigment.g * g->finish.diffuse) * l->color.y * NDotL +
        (g->pigment.g * g->finish.specular) * l->color.y * kspec;
    ret.b = 
        (g->pigment.b * g->finish.diffuse) * l->color.z * NDotL +
        (g->pigment.b * g->finish.specular) * l->color.z * kspec;

    return ret;
}

color_t Raytracer::shadeGaussian(Geometry *g, WLight *l, Ray *r) {
    vec3 normal = normalize(g->getNormal(r));
    vec3 lightDir = normalize(l->location - (r->point + r->t * r->d));
    vec3 H = normalize(lightDir - r->d);
    vec3 R = lightDir - 2.f * dot(normal, lightDir) * normal;
    float NDotL = std::max(0.0f, dot(normal, lightDir));
    float NDotH = std::max(0.0f, dot(normal, H));
    float AngleNH = acos(NDotH);
    float VDotR = std::max(0.0f, dot(-r->d, R));
    float kspec = 0.0;
    color_t ret;
    
    if (g->finish.specular) {
        kspec = exp(-AngleNH * AngleNH /
                    (g->finish.roughness * g->finish.roughness));
    }
 
    ret.r = 
        (g->pigment.r * g->finish.diffuse) * l->color.x * NDotL +
        (g->pigment.r * g->finish.specular) * l->color.x * kspec;
    ret.g = 
        (g->pigment.g * g->finish.diffuse) * l->color.y * NDotL +
        (g->pigment.g * g->finish.specular) * l->color.y * kspec;
    ret.b = 
        (g->pigment.b * g->finish.diffuse) * l->color.z * NDotL +
        (g->pigment.b * g->finish.specular) * l->color.z * kspec;
    ret.f = 1.0;

    return ret;
}

color_t Raytracer::shadeBeckmann(Geometry *g, WLight *l, Ray *r) {
    vec3 normal = normalize(g->getNormal(r));
    vec3 lightDir = normalize(l->location - (r->point + r->t * r->d));
    vec3 H = normalize(lightDir - r->d);
    float NDotL = std::max(0.0f, dot(normal, lightDir));
    float NDotH = std::max(0.0f, dot(normal, H));
    float kspec = 0.0;
    color_t ret;
    
    if (g->finish.specular && NDotH != 0.0f) {
        kspec = exp(-(1.f-NDotH*NDotH) / 
                (g->finish.roughness * g->finish.roughness *
                 NDotH * NDotH)) / (3.1415926f *
                 g->finish.roughness * g->finish.roughness *
                 NDotH * NDotH * NDotH * NDotH);
    }
 
    ret.r = 
        (g->pigment.r * g->finish.diffuse) * l->color.x * NDotL +
        (g->pigment.r * g->finish.specular) * l->color.x * kspec;
    ret.g = 
        (g->pigment.g * g->finish.diffuse) * l->color.y * NDotL +
        (g->pigment.g * g->finish.specular) * l->color.y * kspec;
    ret.b = 
        (g->pigment.b * g->finish.diffuse) * l->color.z * NDotL +
        (g->pigment.b * g->finish.specular) * l->color.z * kspec;
    ret.f = 1.0;

    return ret;
}

color_t Raytracer::shadeCookTorrance(Geometry *g, WLight *l, Ray *r) {
    vec3 normal = normalize(g->getNormal(r));
    vec3 lightDir = normalize(l->location - (r->point + r->t * r->d));
    vec3 H = normalize(lightDir - r->d);
    float NDotL = std::max(0.0f, dot(normal, lightDir));
    float NDotH = std::max(0.0f, dot(normal, H));
    float AngleNH = acos(NDotH);
    float VDotN = std::max(0.0f, dot(normal, -r->d));
    float VDotH = dot(-r->d, H);
    float beckmann = 0.0;
    float fresnel;
    float gatten = 0.0;
    float kspec = 0.0;
    float R0 = (1.f-g->finish.ior)/(1.f+g->finish.ior);
    color_t ret;
    
    if (g->finish.specular && NDotH != 0.0f && VDotN != 0.0 && NDotL != 0.0f) {
        if (g->finish.roughness >= 0.1) {
            beckmann = 0.3 * exp(-(1.f-NDotH*NDotH) / 
                (g->finish.roughness * g->finish.roughness *
                 NDotH * NDotH)) / (3.14159265 *
                 g->finish.roughness * g->finish.roughness *
                 NDotH * NDotH * NDotH * NDotH);
        }
        else {
            /* Use gaussian for shiny materials */
            beckmann = exp(-AngleNH * AngleNH /
                    (g->finish.roughness));
            //cout << beckmann << endl;
        }

        R0 = R0 * R0;
        fresnel = R0 + (1.f - R0) * 
         (NDotH) * (NDotH) * (NDotH) * (NDotH) * (NDotH);

        gatten = std::min(1.f, std::min(2.f*(NDotH)*(VDotN)/(VDotH),
                2.f * (NDotH) * (NDotL) / (VDotH)));

        kspec = beckmann * fresnel * gatten / (3.141592653 * VDotN * NDotL);
    }
 
    ret.r = 
        (g->pigment.r * g->finish.diffuse) * l->color.x * NDotL +
        (g->pigment.r * g->finish.specular) * l->color.x * kspec;
    ret.g = 
        (g->pigment.g * g->finish.diffuse) * l->color.y * NDotL +
        (g->pigment.g * g->finish.specular) * l->color.y * kspec;
    ret.b = 
        (g->pigment.b * g->finish.diffuse) * l->color.z * NDotL +
        (g->pigment.b * g->finish.specular) * l->color.z * kspec;
    ret.f = 1.0;

    return ret;
}

int main(int argc, char *argv[]) {
    string input, temp, line;
    ifstream file;
    int width, height;
    int argOptions = 0;
    Raytracer *tracer;

    /* make sure our random numbers aren't the same every run */
    srand(time(NULL));

    if (argc < NUMARGS) {
        cout << "Usage: raytrace Width Height -I Filename\n";
        exit(1);
    }

    width = atoi(argv[++argOptions]);
    height = atoi(argv[++argOptions]);

    for (argOptions; argOptions < argc; argOptions++) {
        temp = string(argv[argOptions]);
        if (temp.substr(0, 2).compare("-I") == 0) {
        //get the input file name
            if (temp.length() > 2) {
                temp = temp.substr(2);
            }
            else {
                if (argc < argOptions + 1) {
                    cout << "Usage: raytrace Width Height -I Filename\n";
                    exit(1);
                }
                temp = string(argv[++argOptions]);
            }

            input = temp;
        }
        else if (temp.substr(0, 2).compare("-p") == 0) {
            if (temp.length() > 2) {
                temp = temp.substr(2);
            }
            else {
                if (argc < argOptions + 1) {
                    cout << "Usage: raytrace Width Height -I Filename"
                        " [options]\n";
                    exit(1);
                }

                temp = string(argv[++argOptions]);
            }

            SHADING = strtol(temp.c_str(), NULL, 10);
            if (SHADING > 3) {
                cout << "Cannot find shader " << SHADING << endl;
                exit(1);
            }
        }
        else if (temp.substr(0,2).compare("-r") == 0) {
            if (temp.length() > 2) {
                temp = temp.substr(2);
            }
            else {
                if (argc < argOptions + 1) {
                    cout << "Usage: raytrace Width Height -I Filename"
                        " [options]\n";
                    exit(1);
                }

                temp = string(argv[++argOptions]);
            }

            REFLECT = strtol(temp.c_str(), NULL, 10);
        }
        else if (temp.substr(0, 2).compare("-a") == 0) {
            if (temp.length() > 2) {
                temp = temp.substr(2);
            }
            else {
                if (argc < argOptions + 1) {
                    cout << "Usage: raytrace Width Height -I Filename"
                        " [options]\n";
                    exit(1);
                }

                temp = string(argv[++argOptions]);
            }

            ALIAS = strtol(temp.c_str(), NULL, 10);
        }
    }

    
    if (input.compare("") == 0) {
        cout << "Usage: raytrace Width Height -I Filename\n";
        exit(1);
    }

    if (ALIAS <= 0) {
        ALIAS = 1;
    }

    cout << "Reading the scene\n";
    file.open(input.c_str(), ifstream::in);
    if (file.is_open()) {
        parseScene(file);
    }
    else {
        cout << "Could not open file: " << input << endl;
        exit(1);
    }
    cout << "Read in scene, preparing to trace\n";

    //globalScene.debug();
    tracer = new Raytracer(width, height, 
            string(input.substr(0, input.length() - 4)) + ".tga",
            &globalScene);
    /*cout << "Generating rays\n";
    tracer->generateRays();*/
    cout << "Tracing\n";
    tracer->trace();
    cout << "finished\n";

    return 0;
}
